
#if defined(WIN32) || defined(WIN64)
#pragma unmanaged
#endif

#include <Liberty3D/Library/AssetDepotFolder.h>
#include <Liberty3D/Library/AssetImporter.h>
#include <Liberty3D/Library/AssetFolder.h>
#include <Liberty3D/Library/IAsset.h>
#include <Liberty3D/Library/IAssetDepotListener.h>
#include <Liberty3D/Library/Mutex/Local.h>

#include <Liberty3D/Engine/Conversion.h>
#include <Liberty3D/Engine/ISerializer.h>
#include <Liberty3D/Engine/Uuid.h>

#include <Liberty3D/Engine/CacheCleaner.h>
#include <Liberty3D/Engine/ISerializableFactory.h>
#include <Liberty3D/Engine/XML/Parser.h>
#include <Liberty3D/Engine/XML/Serializer.h>
#include <Liberty3D/Engine/XML/INode.h>

#include <algorithm>
#include <streambuf>
#include <fstream>
#include <sstream>

using namespace Liberty::Engine;
using namespace Liberty::Engine::Xml;
using namespace Liberty::Library;

namespace {
    class DepotFactory : public ISerializableFactory {
    protected:
        std::shared_ptr<ISerializable> _depot;
        
    public:
        DepotFactory(std::shared_ptr<ISerializable> depot) : _depot(depot) {
        }
        
        virtual std::shared_ptr<ISerializable> create(const char* name) {
            if (strcmp(name, "database") == 0) {
                return _depot;
            }
            else if (strcmp(name, "folder") == 0) {
                return std::shared_ptr<ISerializable>(new AssetDepotEntryFolder());
            }
            else if (strcmp(name, "file") == 0) {
                return std::shared_ptr<ISerializable>(new AssetDepotEntryFile());
            }
            
            return std::shared_ptr<ISerializable>();
        }
    };
    
    class DepotSerializable : public ISerializable {
    public:
        AssetDepotFolder* _depot;
        
    public:
        DepotSerializable(AssetDepotFolder* depot) : _depot(depot) {
        }
        
    public:
        virtual void serializeObject(ISerializer* s) {
            _depot->serializeObject(s);
        }
        
        virtual const char* serializeClassName() const {
            return "database";
        }
        
        virtual void serializeEnd(ISerializer* s) {
            _depot->serializeEnd(s);
        }
    };
};

void AssetDepotFolder::load() {
    if (AssetFileFolder::exists(".liberty.database.xml")) {
        std::unique_ptr<std::streambuf> buf(AssetFileFolder::open(".liberty.database.xml", std::ios::in));
        
        if (buf) {
            std::shared_ptr<ISerializable>  serializable(new DepotSerializable(this));
            DepotFactory                    factory     (serializable);
            std::unique_ptr<IExclusiveLock> guard       (_entryMutex->lock());
            std::unique_ptr<ISerializer>    reader      (ISerializer::readerForStream(buf.get(), &factory));

            reader->serialize(nullptr, serializable);
        }
        else {
            throw std::runtime_error("Failed to open project's database.");
        }
    }
}

void AssetDepotFolder::save() {
    std::unique_ptr<ISharedLock> guard(_entryMutex->lockShared());

    if (!_libraryDirty) {
        return;
    }

    {
        std::unique_ptr<ISerializer>   writer      (ISerializer::writerForFile(absolutePath(".liberty.database.xml")));
        std::shared_ptr<ISerializable> serializable(new DepotSerializable(this));

        writer->serialize(nullptr, serializable);
    }

    std::unique_ptr<IExclusiveLock> guardEx(guard->upgrade());

    _libraryDirty = false;
    _libraryDirtyTask.reset();
}

void AssetDepotFolder::serializeObject(ISerializer* s) {
    int version = 1;

    s->serialize("version", version);
    s->serialize("root",    _root);
}

void AssetDepotFolder::serializeEnd(ISerializer* s) {
    if (s->isReading()) {
        _map.clear();
        buildMap(_root);
    }
}

void AssetDepotFolder::Processor::operator()(const std::shared_ptr<MappedAsset>& asset, std::unique_ptr<ILock>& lock) {
    std::shared_ptr<IAssetFolder> assetFolder(std::dynamic_pointer_cast<IAssetFolder>(asset));

    if (assetFolder) {
        lock = std::unique_ptr<ILock>(assetFolder->__folderLockShared());

        std::dynamic_pointer_cast<AssetDepotFolder>(asset->assetDepot())->saveLocked(asset);
    }
}

namespace Liberty { namespace Library { namespace Internal {
    class WriterToFile : public Xml::Writer {
    public:
        std::string                       _fileName;
        std::shared_ptr<AssetDepotFolder> _folder;
        std::shared_ptr<AssetDepotEntry>  _entry;

    public:
        WriterToFile(const std::string& fileName, const std::shared_ptr<AssetDepotFolder>& folder, const std::shared_ptr<AssetDepotEntry>& entry) :
          _fileName(fileName),
          _folder  (folder),
          _entry   (entry) {
        }

        virtual ~WriterToFile() {
            close();
            _doc->toXml(_fileName);
            _folder->updateMeta(_entry);
        }
    };
}}}

ISerializer* AssetDepotFolder::serializerForImporter(const Uuid& uuid, bool read) {
    std::string path;
    std::string pathMeta(importerPath(uuid, path));

    if (read) {
        std::shared_ptr<AssetImporter> imp(importer(uuid));
        ImporterFactory*               factory = new ImporterFactory(this);

        return new Xml::Reader(Parser::parse(absolutePath(pathMeta)), factory, true);
    }
    else {
        std::unique_ptr<ISharedLock>       guard(_entryMutex->lockShared());
        AssetDepotEntryMap::const_iterator it   (_map.find(uuid));

        if (it == _map.end()) {
            throw std::runtime_error("UUID " + uuid.toString() + " doesn't exist.");
        }

        const std::shared_ptr<AssetDepotEntry>& entry((*it).second);

        return new Internal::WriterToFile(absolutePath(pathMeta), shared_from_this(), entry);
    }
}

void AssetDepotFolder::saveLocked(const std::shared_ptr<MappedAsset>& asset) {
    std::shared_ptr<AssetImporter> assetImporter = std::dynamic_pointer_cast<AssetImporter>(asset);

    if (assetImporter) {
        saveLocked(assetImporter);
    }
    else {
        throw std::runtime_error("TODO");
    }
}

void AssetDepotFolder::saveLocked(const std::shared_ptr<AssetImporter>& importer) {
    std::unique_ptr<ISerializer>   serial      (serializerForImporter(importer->assetUuid(), false));
    std::shared_ptr<ISerializable> serializable(std::static_pointer_cast<ISerializable>(importer));
    
    serial->serialize(nullptr, serializable);
    serial->close();
}
